#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define N 20

void Mapprint(char Map[N][N], HANDLE hBuffer)
{
    //定义 CHAR_INFO 缓冲区（存储字符 + 颜色）
    CHAR_INFO charBuffer[N + 2][2*N + 2];

    //填充 CHAR_INFO 缓冲区
    //颜色内容：边框：绿色
    //          背景：白色
    //          蛇：黄色
    //          食物：红色
    //上边界
	for(int i = 0; i < 2*N+2; i++)
	{
		charBuffer[0][i].Char.AsciiChar = '-';
		charBuffer[0][i].Attributes = FOREGROUND_GREEN;
		
	}
	//地图内容
    for (int i = 0; i < N; i++)
	{
        //左边界
        charBuffer[i+1][0].Char.AsciiChar = '|';
        charBuffer[i+1][0].Attributes = FOREGROUND_GREEN;

        //地图内容
        for(int j = 0; j < N; j++)
		{
            charBuffer[i+1][2*j+1].Char.AsciiChar = Map[i][j];
            if (Map[i][j] == '@' || Map[i][j] == '*')
                charBuffer[i+1][2*j+1].Attributes = FOREGROUND_RED | FOREGROUND_GREEN | 
				                                    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
            else if (Map[i][j] == 'O')
                charBuffer[i+1][2*j+1].Attributes = FOREGROUND_RED | 
				                                    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
            else
            	charBuffer[i+1][2*j+1].Attributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | 
				                                    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
            charBuffer[i+1][2*j+2].Char.AsciiChar = ' ';
            charBuffer[i+1][2*j+2].Attributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
			                                    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
        }
        //右边界
        charBuffer[i+1][2*N+1].Char.AsciiChar = '|';
		charBuffer[i+1][2*N+1].Attributes = FOREGROUND_GREEN;
    }
    //下边界
	for(int i = 0; i < 2*N+2; i++)
	{
		charBuffer[N+1][i].Char.AsciiChar = '-';
		charBuffer[N+1][i].Attributes = FOREGROUND_GREEN;
	}

    COORD bufferSize = {2*N + 2, N + 2};    //定义写入区域
    COORD bufferCoord = {0, 0};      //从 CHAR_INFO 缓冲区的 (0,0) 开始读取
    SMALL_RECT writeRegion = {0, 0, 2*N + 1, N + 1};   //控制台写入区域

    //使用双缓冲一次性写入字符和颜色
    WriteConsoleOutput(
        hBuffer,    //控制台的屏幕缓冲区（直接输出的缓冲区）
        (CHAR_INFO*)charBuffer,     // CHAR_INFO 缓冲区（预先准备的写入了字符和颜色的缓冲区）
        bufferSize,    //写入的区域
        bufferCoord,    //读取起始点
        &writeRegion    //控制台写入区域
    );

    //重置光标
    SetConsoleCursorPosition(hBuffer, (COORD){0, 0});
}

int main()
{
	//定义双缓冲的句柄
	HANDLE hBuffer = CreateConsoleScreenBuffer(
	    GENERIC_READ | GENERIC_WRITE,	//缓冲区设置为可读（输出）可写（改变状态）
	    FILE_SHARE_READ | FILE_SHARE_WRITE,
	    NULL,	//默认安全属性
	    CONSOLE_TEXTMODE_BUFFER,	//应为程序由字符呈现，故采用文本模式的缓冲区
	    NULL	//无模板
	);
	SetConsoleActiveScreenBuffer(hBuffer); 		//设置为活动缓冲区
	
    //定义并清洗地图
    char Map[N][N];
    for(int i = 0; i < N; i++)
	{
		for(int j = 0; j < N; j++)
		{
			Map[i][j] = ' ';
		}
	}
    //定义蛇的长度及所有部位的坐标
    int length = 3, snake[N*N][2];
	//定义初始蛇各个部位的坐标
    snake[0][0] = N/2; snake[0][1] = N/2;
    snake[1][0] = N/2 + 1; snake[1][1] = N/2;
    snake[2][0] = N/2 + 2; snake[2][1] = N/2;
    //定义食物的坐标
    int x, y, direction = 1;
    //随机生成食物
    srand(time(NULL));
    x = rand() % N;
    y = rand() % N;
    Map[x][y] = 'O';
    //划归蛇的位置
	Map[snake[0][0]][snake[0][1]] = '@';
	Map[snake[1][0]][snake[1][1]] = '*';
	Map[snake[2][0]][snake[2][1]] = '*';
    //打印地图
	Mapprint(Map, hBuffer);;
    printf("2s后开始\n");
    Sleep(2000);
    while (1)
	{
        //蛇的变化：将尾巴置为空格，所有部位向前移动一个，覆盖掉原本头的位置
		//保存蛇尾的位置，便于失败后恢复蛇尾以及增加长度
        Map[snake[length-1][0]][snake[length-1][1]] = ' ';
		int tail[2] = {snake[length-1][0], snake[length-1][1]};
        for(int i = length-1; i > 0; i--)
		{
			snake[i][0] = snake[i-1][0];
			snake[i][1] = snake[i-1][1];
		}
        //根据存储的方向确定头的下一个位置
        switch (direction)
        {
        case 1:
            snake[0][0]--;
            break;
        case 2:
            snake[0][1]--;
            break;
        case 3:
            snake[0][0]++;
            break;
        case 4:
            snake[0][1]++;
            break;
        default:
            break;
        }
        //边界限制
        if(snake[0][0] < 0 || snake[0][0] >= N || snake[0][1] < 0 || snake[0][1] >= N || Map[snake[0][0]][snake[0][1]] == '*')
		{
			//恢复蛇尾并打印地图
			Map[tail[0]][tail[1]] = '*';
			//上边界
				for(int i = 0; i < 2*N+2; i++)
				    printf("-");
				printf("\n");
				//打印地图
			    for(int i = 0; i < N; i++)
				{
					//左边界
					printf("|");
					for(int j = 0; j < N; j++)
					{
						printf("%c ",Map[i][j]);
					}
					//右边界
					printf("|");
					printf("\n");
				}
				//下边界
				for(int i = 0; i < 2*N+2; i++)
				    printf("-");
				printf("\n");
            break;
		}
        //检测是否吃到食物
        if (Map[snake[0][0]][snake[0][1]] == 'O')
		{
            //吃到食物后增加蛇的长度，增加的部分即原来的蛇尾
            length++;
            Map[tail[0]][tail[1]] = '*';
            snake[length-1][0] = tail[0]; snake[length-1][1] = tail[1];
			//确保食物的生成合法
			while(1)
			{
				x = rand() % N;
				y = rand() % N;
				if(Map[x][y] == ' ')
				{
					Map[x][y] = 'O';
					break;
				}
			}
		}
		//重新划归蛇的位置
    	Map[snake[0][0]][snake[0][1]] = '@';
		Map[snake[1][0]][snake[1][1]] = '*';
		Map[snake[2][0]][snake[2][1]] = '*';
		//打印地图
        Mapprint(Map, hBuffer);;
        // 检测WASD键状态
        short state = GetAsyncKeyState('W');
        if (state & 0x8000)
		{
            direction = 1;
        }
        state = GetAsyncKeyState('A');
        if (state & 0x8000)
		{
            direction = 2;
        }
		state = GetAsyncKeyState('S');
        if (state & 0x8000)
		{
            direction = 3;
        }
		state = GetAsyncKeyState('D');
        if (state & 0x8000)
		{
            direction = 4;
        }
        // 检测F1键状态
        state = GetAsyncKeyState(VK_F1);
        if (state & 0x8000)
		{
            break;
        }
        Sleep(100); // 暂停100毫秒
    }
    //释放资源
    SetConsoleActiveScreenBuffer(GetStdHandle(STD_OUTPUT_HANDLE));
	CloseHandle(hBuffer);
    printf("程序结束\n");
    return 0;
}
